为什么需要Monorepo
在开发组件库项目时,将Vue Template项目中的核心组件拆离出来,会遇到几个典型问题:
问题一:不能在同一个项目中打包组件库
components文件夹属于vue-template项目,直接在其中加入vite配置文件来打包组件库会导致两个问题:
- 业务组件被随意修改后,其他依赖这些组件的项目会受影响
- 业务类型页面和组件库的库模式不能在同一个vite.config.ts中配置——即使项目还没涉及业务逻辑,配置文件已经几百行了,再加入库模式会让文件变得更加复杂
unplugin-vue-components插件默认自动导入src/components目录下的组件,如果把仅适用于当前项目的私有组件和公共基础组件混在一起,统一打包配置会导致无关组件被打包进来,甚至出现打包错误
问题二:playground冗余
如果两个项目放在同一目录下开发,vue3-template中已经有components里所有的示例,为什么还需要在components中单独维护一个playground?这明显是重复工作。
问题三:npm link/pnpm link不稳定
本地开发组件库时,使用npm link或pnpm link连接本地包并不总是可靠,经常需要清除缓存或重新执行link操作。
Monorepo的解决方案
Monorepo(单体仓库)将多个相关联的项目放在同一个仓库中管理,是解决上述三个问题的标准方案。核心优势:
| 优势 | 说明 |
|---|---|
| 统一管理 | 组件库、模板项目、CLI工具等共享同一仓库 |
| 依赖共享 | 公共依赖只需安装一次,减少重复 |
| 本地引用 | 包之间直接引用,无需link或发布 |
| 统一构建 | 一次命令构建所有包,缓存复用提升速度 |
| 版本协调 | 依赖版本统一管理,避免版本冲突 |
Monorepo vs 传统多仓库
主流Monorepo工具对比
| 工具 | 特点 | 适用场景 | 缓存能力 |
|---|---|---|---|
| pnpm workspaces | 内置workspace支持,磁盘效率高 | 中小型项目 | 无内置缓存 |
| Turborepo | 构建缓存、并行执行、远程缓存 | 中大型项目 | 本地+远程缓存 |
| Nx | 任务图分析、增量构建、插件生态 | 大型企业项目 | 本地+远程缓存 |
| Lerna | 历史最悠久,发包管理能力强 | 需要独立发包的库 | 需配合Nx/Turborepo |
项目结构规划
本课程项目采用pnpm workspaces + Turborepo的方案:
project-root/
├── packages/
│ ├── components/ # 组件库
│ │ ├── src/
│ │ ├── vite.config.ts # 库模式打包配置
│ │ └── package.json
│ ├── template/ # 管理后台模板
│ │ ├── src/
│ │ ├── vite.config.ts # 应用模式配置
│ │ └── package.json
│ └── cli/ # CLI工具
│ ├── src/
│ └── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── package.json
text
workspace引用配置
在pnpm workspaces中,包之间的引用使用 workspace:* 协议:
// packages/template/package.json
{
"dependencies": {
"@project/components": "workspace:*"
}
}
json
这样做的好处是:修改components中的代码后,template项目会立即感知到变化,无需重新link或发布。
组件库打包与业务项目的关系
关键原则:库模式配置独立于应用模式配置
组件库打包使用Vite的库模式(Library Mode),配置独立于业务项目的应用模式:
// packages/components/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'Components',
fileName: (format) => `components.${format}.js`
},
rollupOptions: {
external: ['vue', 'element-plus'],
output: {
globals: {
vue: 'Vue'
}
}
}
}
})
typescript
这种分离确保了:
- 组件库只导出公共组件,私有组件不会被打包
- 打包配置清晰独立,不会与业务配置混淆
- 组件库可以独立发布到npm或私有仓库
↑